﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace Screenshot {
  public partial class Form1: Form {
    public Form1() {
      InitializeComponent();

      // 解决窗口闪烁的问题
      SetStyle(ControlStyles.UserPaint | ControlStyles.ResizeRedraw | ControlStyles.AllPaintingInWmPaint | ControlStyles.OptimizedDoubleBuffer, true);
    }
    #region 基本设置参数
    /// <summary>
    /// 截图时是否显示截图信息栏
    /// </summary>
    public bool InfoBoxVisible = true;
    /// <summary>
    /// 截图时是否显示编辑工具栏
    /// </summary>
    public bool ToolBoxVisible = true;
    /// <summary>
    /// 截图中是否包含鼠标指针形状
    /// </summary>
    public bool IsCutCursor = true;

    /// <summary>
    /// 截图时是否显示放大镜
    /// </summary>
    public bool ZoomBoxVisible{ get; set; } = true;

    /// <summary>
    /// 放大镜的尺寸——宽度
    /// </summary>
    public int ZoomBoxWidth1{ get; set; } = 120;

    /// <summary>
    /// 放大镜的尺寸——高度
    /// </summary>
    public int ZoomBoxHeight1{ get; set; } = 100;

    #endregion

    #region 图片上传参数
    public string PicDescFieldName = "pictitle";
    public string ImageFieldName = "upfile";
    public string PicDesc = "cutImage";
    public string UploadUrl = "http://";
    public bool DoUpload = false;
    #endregion

    #region 自动保存参数
    /// <summary>
    /// 是否自动保存到硬盘
    /// </summary>
    public bool AutoSaveToDisk = false;
    /// <summary>
    /// 自动保存目录
    /// </summary>
    public string AutoSaveDirectory = string.Empty;
    /// <summary>
    /// 是否启用日期格式“2013_02_22”的子目录
    /// </summary>
    public bool AutoSaveSubDir = false;
    /// <summary>
    /// 自动保存文件名前缀
    /// </summary>
    public string AutoSaveFileName1 = "屏幕截图";
    /// <summary>
    /// 自动文件名规则：日期时间，日期_序号，序号
    /// </summary>
    public string AutoSaveFileName2 = "日期时间";
    /// <summary>
    /// 自动保存文件格式：.png, .jpg, .jpeg, .gif, .bmp
    /// </summary>
    public string AutoSaveFileName3 = ".png";
    /// <summary>
    /// 自动保存文件名序号
    /// </summary>
    public int autoSaveFileIndex = 0;
    #endregion 自动保存参数



    /// <summary>
    /// 向全局原子表添加一个字符串，并返回这个字符串的唯一标识符（原子ATOM）
    /// </summary>
    /// <param name="lpString">自己设定的一个字符串</param>
    /// <returns></returns>
    [DllImport("Kernel32.dll")]
    public static extern int GlobalAddAtom(string lpString);

    /// <summary>
    /// 注册热键
    /// </summary>
    /// <param name="hwnd"></param>
    /// <param name="id"></param>
    /// <param name="fsModifiers"></param>
    /// <param name="vk"></param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    public static extern bool RegisterHotKey(IntPtr hwnd, int id, uint fsModifiers, Keys vk);
    /// <summary>
    /// 取消热键注册
    /// </summary>
    /// <param name="hwnd"></param>
    /// <param name="id"></param>
    /// <returns></returns>
    [DllImport("user32.dll")]
    public static extern bool UnregisterHotKey(IntPtr hwnd, int id);

    /// <summary>
    /// 热键ID
    /// </summary>
    public int HotKeyId { get; set; } = 100;
    /// <summary>
    /// 热键模式:0=Ctrl + Alt + A, 1=Ctrl + Shift + A
    /// </summary>
    public int HotKeyMode { get; set; } = 1;

    /// <summary>
    /// 控制键的类型
    /// </summary>
    public enum KeyModifiers: uint {
      None = 0,
      Alt = 1,
      Control = 2,
      Shift = 4,
      Windows = 8,
    }
    /// <summary>
    /// 用于保存截取的整个屏幕的图片
    /// </summary>
    protected Bitmap screenImage { get; set; }

    #region 截图基本变量
    /// <summary>
    /// 用于判断是否已经开始截图，控制信息框是否显示。
    /// </summary>
    private bool isCuting { get; set; }
    /// <summary>
    /// 鼠标按下的点
    /// </summary>
    private Point beginPoint { get; set; }
    /// <summary>
    /// 最终确定的绘制基点
    /// </summary>
    private Point endPoint { get; set; }
    /// <summary>
    /// 用于记录截图显示区域的大小（包括调整块的区域，调整区域边框宽度2px）
    /// </summary>
    private Rectangle cutImageRect { get; set; } = new Rectangle(0, 0, 5, 5);

    #endregion
    /// <summary>
    /// 更新UI的模式，用于标记哪些需要显示，哪些需要隐藏；
    /// </summary>
    [Flags]
    public enum UpdateUIMode: uint {
      //值得注意的是，如果要使用组合值，那么就不能用连接的数字表示，必须是几何级增长！
      None = 0,
      ShowTextPro = 1,
      ShowPenStyle = 2,
      ShowToolBox = 4,
      ShowInfoBox = 8,
      ShowZoomBox = 16,
      ShowCutImage = 32,
      HideTextPro = 64,
      HidePenStyle = 128,
      HideToolBox = 256,
      HideInfoBox = 512
    }

    /// <summary>
    /// 计算并保存截图的区域框的大小
    /// </summary>
    /// <param name="beginPoint"></param>
    /// <param name="endPoint"></param>
    private void SaveCutImageSize(Point beginPoint, Point endPoint) {
      // 保存最终的绘图基点，用于截取选中的区域
      this.endPoint = beginPoint;
      // 计算截取图片的大小
      int imageWidth = Math.Abs(endPoint.X - beginPoint.X) + 1;
      int imageHeight = Math.Abs(endPoint.Y - beginPoint.Y) + 1;
      int labelWidth = imageWidth + 4;
      int labelHeight = imageHeight + 4;
      // 设置截图区域的位置和大小
      cutImageRect = new Rectangle(beginPoint.X - 2, beginPoint.Y - 2, labelWidth, labelHeight);
    }

    /// <summary>
    /// 执行截图,将选定区域的图片保存到剪贴板
    /// </summary>
    /// <param name="saveToDisk">是否将图片保存到磁盘</param>
    /// <param name="uploadImage"></param>
    private void ExecCutImage(bool saveToDisk, bool uploadImage) //bool saveToDisk = false, bool uploadImage = false
    {
      // 如果图片获取区域不可见,则退出保存图片过程
      if(!CutImageLbl.Visible) { return; }
      Rectangle srcRect = new Rectangle();
      srcRect.X = CutImageLbl.Location.X + 2;
      srcRect.Y = CutImageLbl.Location.Y + 2;
      srcRect.Width = CutImageLbl.Width - 4;
      srcRect.Height = CutImageLbl.Height - 4;
      Rectangle destRect = new Rectangle(0, 0, srcRect.Width, srcRect.Height);
      Bitmap bmp = new Bitmap(srcRect.Width, srcRect.Height);
      Graphics g = Graphics.FromImage(bmp);
      g.DrawImage(screenImage, destRect, srcRect, GraphicsUnit.Pixel);

      Clipboard.SetImage(bmp);

      ExitCutImage(true);
    }
    /// <summary>
    /// 退出截图过程
    /// </summary>
    private void ExitCutImage(bool hideWindow) //  = true
    {
      CutImageLbl.Visible = false;
      isCuting = false;

      if(hideWindow) {
        screenImage.Dispose();
        Hide();
      }
    }

    // 加载窗体的时候：注册热键
    private void Form1_Load(object sender, EventArgs e) {
      // 隐藏
      CutImageLbl.Hide();

      // 设置托盘图标
      notifyIcon1.Icon = Properties.Resources.cut;
      notifyIcon1.Visible = true;
      notifyIcon1.ContextMenuStrip = contextMenuStrip1;

      // 隐藏窗口
      Hide();
      // 注册快捷键
      // 注：HotKeyId的合法取之范围是0x0000到0xBFFF之间，GlobalAddAtom函数得到的值在0xC000到0xFFFF之间，
      // 所以减掉0xC000来满足调用要求。
      HotKeyId = GlobalAddAtom("Screenshot") - 0xC000;
      if(HotKeyId == 0) {
        HotKeyId = 0xBFFE;
      }

      if(HotKeyMode == 0) {
        RegisterHotKey(Handle, HotKeyId, (uint)KeyModifiers.Control | (uint)KeyModifiers.Alt, Keys.A);
      } else {
        RegisterHotKey(Handle, HotKeyId, (uint)KeyModifiers.Control | (uint)KeyModifiers.Shift, Keys.A);
      }


    }
    /// <summary>
    /// 处理快捷键事件
    /// </summary>
    /// <param name="m"></param>
    protected override void WndProc(ref Message m) {
      const int WM_HOTKEY = 0x0312;
      switch(m.Msg) {
        case WM_HOTKEY:
          ShowForm();
          break;
      }
      base.WndProc(ref m);
    }


    /// <summary>
    /// 如果窗口为可见状态，则隐藏窗口；
    /// 否则则显示窗口
    /// </summary>
    protected void ShowForm() {
      if(Visible) {
        Hide();
        return;
      }
      // 截图窗口实现原理
      // 截图窗口实际是一个没有边框，没有菜单，没有工具栏的一个全屏顶层窗口。
      //
      // 当按下热键时，程序首先获取整个屏幕的图片，保存到“screenImage”变量中；
      // 然后添加遮罩层，将其设置为窗体的背景图，将窗口大小设置为主屏幕的大小，显示窗口；
      // 让人感觉是在桌面上加一个半透明的遮罩层一样。
      //
      Bitmap bkImage = new Bitmap(Screen.AllScreens[0].Bounds.Width, Screen.AllScreens[0].Bounds.Height);
      Graphics g = Graphics.FromImage(bkImage);
      g.CopyFromScreen(new Point(0, 0), new Point(0, 0), Screen.AllScreens[0].Bounds.Size,
        CopyPixelOperation.SourceCopy);
      screenImage = (Bitmap)bkImage.Clone();
      g.FillRectangle(new SolidBrush(Color.FromArgb(64, Color.Gray)), Screen.PrimaryScreen.Bounds);

      BackgroundImage = bkImage;

      ShowInTaskbar = false;
      FormBorderStyle = FormBorderStyle.None;
      Width = Screen.PrimaryScreen.Bounds.Width;
      Height = Screen.PrimaryScreen.Bounds.Height;
      Location = Screen.PrimaryScreen.Bounds.Location;

      WindowState = FormWindowState.Maximized;
      Show();
    }

    // 关闭窗口时，要取消热键注册
    private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
      if(e.CloseReason == CloseReason.ApplicationExitCall) {
        e.Cancel = false;
        UnregisterHotKey(Handle, HotKeyId);
        return;
      }
      Hide();
      e.Cancel = true;
    }

    private void button1_Click(object sender, EventArgs e) {
      Application.Exit();
    }

    private void 退出ToolStripMenuItem_Click(object sender, EventArgs e) {
      Application.Exit();
    }

    // 截图窗口鼠标按下事件处理程序
    private void Form1_MouseDown(object sender, MouseEventArgs e) {
      // 左键单击事件
      if(e.Button == MouseButtons.Left && e.Clicks == 1) {
        if(!CutImageLbl.Visible) {
          isCuting = true;
          beginPoint = e.Location;
          endPoint = e.Location;
          SaveCutImageSize(e.Location, e.Location);

          UpdateCutInfoLabel(UpdateUIMode.ShowCutImage | UpdateUIMode.ShowInfoBox);
        }
      }
      // 左键双击事件
      if(e.Button == MouseButtons.Left && e.Clicks == 2) {
        if(CutImageLbl.Visible) {
          ExecCutImage(false, false);
        }

      }
      // 右键单击事件
      if(e.Button == MouseButtons.Right) {
        ExitCutImage(!CutImageLbl.Visible);
      }

    }
    // 截图窗口鼠标移动事件处理程序
    private void Form1_MouseMove(object sender, MouseEventArgs e) {
      // 如果截取区域不可见,则退出处理过程
      if(!CutImageLbl.Visible) {
        UpdateCutInfoLabel(UpdateUIMode.None);
        return;
      }

      Point pntBgn = beginPoint;
      Point pntEnd = e.Location;

      // 如果是反向拖动，重新设置起始点
      if(e.Location.X < beginPoint.X && e.Location.Y < beginPoint.Y) {
        pntBgn = e.Location;
        pntEnd = beginPoint;
      } else {
        if(e.Location.X < beginPoint.X) {
          pntBgn = new Point(e.Location.X, beginPoint.Y);
          pntEnd = new Point(beginPoint.X, e.Location.Y);
        } else {
          if(e.Location.Y < beginPoint.Y) {
            pntBgn = new Point(beginPoint.X, e.Location.Y);
            pntEnd = new Point(e.Location.X, beginPoint.Y);
          }
        }
      }

      if(isCuting) {
        SaveCutImageSize(pntBgn, pntEnd);
      }

      UpdateCutInfoLabel(UpdateUIMode.None);
    }

    /// <summary>
    /// 记录鼠标上一次移动的时间
    /// </summary>
    private long lastMouseMoveTime = DateTime.Now.Ticks;

    /// <summary>
    /// 更新截图信息显示框，截图编辑工具框
    /// </summary>
    private void UpdateCutInfoLabel(UpdateUIMode updateUIMode) // UpdateUIMode updateUIMode = UpdateUIMode.None
    {
      //大于300毫秒或有组件显示或隐藏才进行重绘
      long mouseMoveTimeStep = DateTime.Now.Ticks - lastMouseMoveTime;
      if(mouseMoveTimeStep < 300 && updateUIMode == UpdateUIMode.None) { return; }
      lastMouseMoveTime = DateTime.Now.Ticks;

      if(CutImageLbl.Visible || (updateUIMode & UpdateUIMode.ShowCutImage) != UpdateUIMode.None) {
        CutImageLbl.SetBounds(cutImageRect.Left, cutImageRect.Top, cutImageRect.Width, cutImageRect.Height, BoundsSpecified.All);
        if(!CutImageLbl.Visible) {
          CutImageLbl.Show();
        }
      }
    }

    // 截图窗口鼠标抬起事件处理程序
    private void Form1_MouseUp(object sender, MouseEventArgs e) {
      if(e.Button == MouseButtons.Left) {
        if(isCuting) {
          isCuting = false;

          UpdateCutInfoLabel(UpdateUIMode.None);
        }
      }

    }
    // 截取区域图片的绘制事件处理程序
    private void CutImageLbl_Paint(object sender, PaintEventArgs e) {
      int imgWidth = CutImageLbl.Width - 4;
      int imgHeight = CutImageLbl.Height - 4;
      if(imgWidth < 1) { imgWidth = 1; }
      if(imgHeight < 1) { imgHeight = 1; }

      // 创建缓存图像，先将要绘制的内容全部绘制到缓存中，最后再一次性绘制到 Label 上，
      // 这样可以提高性能，并且可以防止屏幕闪烁的问题
      Bitmap bmp_lbl = new Bitmap(CutImageLbl.Width, CutImageLbl.Height);
      Graphics g = Graphics.FromImage(bmp_lbl);

      // 将要截取的部分绘制到缓存
      Rectangle destRect = new Rectangle(2, 2, imgWidth, imgHeight);
      Point srcPoint = CutImageLbl.Location;
      srcPoint.Offset(2, 2);
      Rectangle srcRect = new Rectangle(srcPoint, new Size(imgWidth, imgHeight));
      g.DrawImage(screenImage, destRect, srcRect, GraphicsUnit.Pixel);

      SolidBrush brush = new SolidBrush(Color.FromArgb(10, 124, 202));
      Pen pen = new Pen(brush, 1.0F);

      //以下部分（边框和调整块）的绘制放在（编辑内容）的后面，是解决绘制编辑内容会覆盖（边框和调整块）的问题

      // 绘制边框外的区域，解决会被编辑内容覆盖的问题
      // 上边
      destRect = new Rectangle(0, 0, CutImageLbl.Width, 2);
      srcPoint = CutImageLbl.Location;
      //srcPoint.Offset(2, 2);
      srcRect = new Rectangle(srcPoint, new Size(CutImageLbl.Width, 2));
      g.DrawImage(BackgroundImage, destRect, srcRect, GraphicsUnit.Pixel);

      // 下边
      destRect = new Rectangle(0, CutImageLbl.Height - 2, CutImageLbl.Width, 2);
      srcPoint = CutImageLbl.Location;
      srcPoint.Offset(0, CutImageLbl.Height - 2);
      srcRect = new Rectangle(srcPoint, new Size(CutImageLbl.Width, 2));
      g.DrawImage(BackgroundImage, destRect, srcRect, GraphicsUnit.Pixel);

      // 左边
      destRect = new Rectangle(0, 2, 2, CutImageLbl.Height - 4);
      srcPoint = CutImageLbl.Location;
      srcPoint.Offset(0, 2);
      srcRect = new Rectangle(srcPoint, new Size(2, CutImageLbl.Height - 4));
      g.DrawImage(BackgroundImage, destRect, srcRect, GraphicsUnit.Pixel);

      // 右边
      destRect = new Rectangle(CutImageLbl.Width - 2, 2, 2, CutImageLbl.Height - 4);
      srcPoint = CutImageLbl.Location;
      srcPoint.Offset(CutImageLbl.Width - 2, 2);
      srcRect = new Rectangle(srcPoint, new Size(2, CutImageLbl.Height - 4));
      g.DrawImage(BackgroundImage, destRect, srcRect, GraphicsUnit.Pixel);

      // 绘制边框
      g.DrawLine(pen, 2, 2, CutImageLbl.Width - 3, 2);
      g.DrawLine(pen, 2, 2, 2, CutImageLbl.Height - 3);
      g.DrawLine(pen, CutImageLbl.Width - 3, 2, CutImageLbl.Width - 3, CutImageLbl.Height - 3);
      g.DrawLine(pen, 2, CutImageLbl.Height - 3, CutImageLbl.Width - 3, CutImageLbl.Height - 3);

      // 绘制四个角的调整块
      g.FillRectangle(brush, 0, 0, 4, 5);
      g.FillRectangle(brush, CutImageLbl.Width - 4, 0, 4, 5);
      g.FillRectangle(brush, 0, CutImageLbl.Height - 5, 4, 5);
      g.FillRectangle(brush, CutImageLbl.Width - 4, CutImageLbl.Height - 5, 4, 5);

      // 绘制中间的四个调整块
      int blockX = CutImageLbl.Width / 2 - 2;
      int blockY = CutImageLbl.Height / 2 - 2;
      g.FillRectangle(brush, blockX, 0, 4, 5);
      g.FillRectangle(brush, 0, blockY, 4, 5);
      g.FillRectangle(brush, blockX, CutImageLbl.Height - 5, 4, 5);
      g.FillRectangle(brush, CutImageLbl.Width - 4, blockY, 4, 5);

      // 绘制到 Label 上
      e.Graphics.DrawImage(bmp_lbl, 0, 0);
      bmp_lbl.Dispose();
    }
    // 截取区域图片的鼠标按下事件处理程序
    private void CutImageLbl_MouseDown(object sender, MouseEventArgs e) {
      // 左键双击事件
      if(e.Button == MouseButtons.Left && e.Clicks == 2) {
        if(CutImageLbl.Visible) {
          ExecCutImage(false, false);
        }
      }

    }
    // 托盘菜单设置事件处理程序
    private void tsmi_Set_Click(object sender, EventArgs e) {
      FrmSetup setup = new FrmSetup(Handle);
      setup.ShowDialog();
    }
  }
}
